package com.icongyou.enterprise.talent_analysis.scheduler;

import com.icongyou.enterprise.data_aggregation.entity.GradeEntity;
import com.icongyou.enterprise.data_aggregation.entity.SubmissionEntity;
import com.icongyou.enterprise.data_aggregation.mapper.DataAggregationMapper;
import com.icongyou.enterprise.talent_analysis.event.StudentDataChangeEvent;
import com.icongyou.enterprise.talent_analysis.mapper.DataSyncLogMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.context.ApplicationEventPublisher;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * 学生数据同步调度器测试
 */
@ExtendWith(MockitoExtension.class)
@DisplayName("学生数据同步调度器测试")
class StudentDataSyncSchedulerTest {

    @Mock
    private DataAggregationMapper dataAggregationMapper;

    @Mock
    private DataSyncLogMapper dataSyncLogMapper;

    @Mock
    private ApplicationEventPublisher eventPublisher;

    @InjectMocks
    private StudentDataSyncScheduler scheduler;

    @Test
    @DisplayName("syncStudentDataChanges - 无新数据时不处理")
    void testSyncStudentDataChanges_NoNewData() {
        // Arrange
        when(dataSyncLogMapper.getLastSyncTime("submissions"))
            .thenReturn(LocalDateTime.now().minusHours(1));
        when(dataSyncLogMapper.getLastSyncTime("grades"))
            .thenReturn(LocalDateTime.now().minusHours(1));
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        verify(dataAggregationMapper).selectAllNewSubmissionsSince(any());
        verify(dataAggregationMapper).selectAllNewGradesSince(any());
        verify(eventPublisher, never()).publishEvent(any());
    }

    @Test
    @DisplayName("syncStudentDataChanges - 有新提交时触发事件")
    void testSyncStudentDataChanges_WithNewSubmissions() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        SubmissionEntity submission = createSubmission("660e8400-e29b-41d4-a716-446655440105");
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.singletonList(submission));
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        ArgumentCaptor<StudentDataChangeEvent> eventCaptor = 
            ArgumentCaptor.forClass(StudentDataChangeEvent.class);
        verify(eventPublisher, atLeastOnce()).publishEvent(eventCaptor.capture());
        
        StudentDataChangeEvent event = eventCaptor.getValue();
        assertEquals("660e8400-e29b-41d4-a716-446655440105", event.getStudentId());
        assertEquals("SUBMISSION_SYNC", event.getChangeType());
        verify(dataSyncLogMapper, atLeastOnce()).insert(any());
    }

    @Test
    @DisplayName("syncStudentDataChanges - 团队作业通知所有成员")
    void testSyncStudentDataChanges_TeamSubmission() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        SubmissionEntity teamSubmission = createTeamSubmission(
            "660e8400-e29b-41d4-a716-446655440105",
            Arrays.asList("660e8400-e29b-41d4-a716-446655440105", "660e8400-e29b-41d4-a716-446655440103")
        );
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.singletonList(teamSubmission));
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        ArgumentCaptor<StudentDataChangeEvent> eventCaptor = 
            ArgumentCaptor.forClass(StudentDataChangeEvent.class);
        verify(eventPublisher, atLeast(2)).publishEvent(eventCaptor.capture());
        
        List<String> studentIds = eventCaptor.getAllValues().stream()
            .map(StudentDataChangeEvent::getStudentId)
            .distinct()
            .collect(java.util.stream.Collectors.toList());
        assertTrue(studentIds.contains("660e8400-e29b-41d4-a716-446655440105"));
        assertTrue(studentIds.contains("660e8400-e29b-41d4-a716-446655440103"));
    }

    @Test
    @DisplayName("syncStudentDataChanges - 首次同步使用24小时前时间")
    void testSyncStudentDataChanges_FirstSync() {
        // Arrange
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(null);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(null);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        ArgumentCaptor<LocalDateTime> timeCaptor = ArgumentCaptor.forClass(LocalDateTime.class);
        verify(dataAggregationMapper).selectAllNewSubmissionsSince(timeCaptor.capture());
        
        LocalDateTime capturedTime = timeCaptor.getValue();
        assertTrue(capturedTime.isBefore(LocalDateTime.now().minusHours(23)));
        assertTrue(capturedTime.isAfter(LocalDateTime.now().minusHours(25)));
    }

    @Test
    @DisplayName("syncStudentDataChanges - 异常不中断调度")
    void testSyncStudentDataChanges_ExceptionHandling() {
        // Arrange
        when(dataSyncLogMapper.getLastSyncTime(anyString()))
            .thenThrow(new RuntimeException("数据库错误"));

        // Act - 不应抛出异常
        scheduler.syncStudentDataChanges();

        // Assert - 异常被捕获，调度继续
        verify(dataSyncLogMapper, atLeast(1)).getLastSyncTime(anyString());
    }

    @Test
    @DisplayName("syncStudentDataChanges - 批量学生去重")
    void testSyncStudentDataChanges_DuplicateStudents() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        // 同一个学生的多个提交
        SubmissionEntity sub1 = createSubmission("660e8400-e29b-41d4-a716-446655440105");
        SubmissionEntity sub2 = createSubmission("660e8400-e29b-41d4-a716-446655440105");
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Arrays.asList(sub1, sub2));
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert - 同一学生只触发一次事件
        ArgumentCaptor<StudentDataChangeEvent> eventCaptor = 
            ArgumentCaptor.forClass(StudentDataChangeEvent.class);
        verify(eventPublisher, times(1)).publishEvent(eventCaptor.capture());
        assertEquals("660e8400-e29b-41d4-a716-446655440105", eventCaptor.getValue().getStudentId());
    }

    @Test
    @DisplayName("syncStudentDataChanges - 有新成绩时触发事件")
    void testSyncStudentDataChanges_WithNewGrades() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        
        GradeEntity grade = createGrade("submission-1");
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.singletonList(grade));

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        verify(dataAggregationMapper).selectAllNewGradesSince(any());
        // 由于成绩同步逻辑复杂（需要查询submission），可能不触发事件或触发但有错误处理
        // 这里主要验证方法被调用
    }

    @Test
    @DisplayName("syncSubmissions - 空列表时提前返回")
    void testSyncSubmissions_EmptyList() {
        // Arrange
        when(dataSyncLogMapper.getLastSyncTime("submissions"))
            .thenReturn(LocalDateTime.now().minusHours(1));
        when(dataSyncLogMapper.getLastSyncTime("grades"))
            .thenReturn(LocalDateTime.now().minusHours(1));
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        verify(eventPublisher, never()).publishEvent(any());
        verify(dataSyncLogMapper, never()).insert(any());
    }

    @Test
    @DisplayName("syncSubmissions - 多个学生多次提交")
    void testSyncSubmissions_MultipleStudentsMultipleSubmissions() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        List<SubmissionEntity> submissions = Arrays.asList(
            createSubmission("student-1"),
            createSubmission("student-1"), // 同一学生的第二次提交
            createSubmission("student-2"),
            createSubmission("student-3")
        );
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(submissions);
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        ArgumentCaptor<StudentDataChangeEvent> eventCaptor = 
            ArgumentCaptor.forClass(StudentDataChangeEvent.class);
        verify(eventPublisher, times(3)).publishEvent(eventCaptor.capture()); // 3个不同学生
        
        List<String> studentIds = eventCaptor.getAllValues().stream()
            .map(StudentDataChangeEvent::getStudentId)
            .distinct()
            .collect(java.util.stream.Collectors.toList());
        assertEquals(3, studentIds.size());
        assertTrue(studentIds.contains("student-1"));
        assertTrue(studentIds.contains("student-2"));
        assertTrue(studentIds.contains("student-3"));
    }

    @Test
    @DisplayName("syncSubmissions - 团队作业包含贡献度为null")
    void testSyncSubmissions_TeamWithNullContribution() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        SubmissionEntity teamSubmission = createSubmission("student-1");
        teamSubmission.setTeamId("team-1");
        teamSubmission.setContributionRate(null); // 无贡献度数据
        
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.singletonList(teamSubmission));
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        ArgumentCaptor<StudentDataChangeEvent> eventCaptor = 
            ArgumentCaptor.forClass(StudentDataChangeEvent.class);
        verify(eventPublisher, times(1)).publishEvent(eventCaptor.capture()); // 只通知主提交者
        assertEquals("student-1", eventCaptor.getValue().getStudentId());
    }

    @Test
    @DisplayName("syncSubmissions - 日志记录验证")
    void testSyncSubmissions_LogRecording() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        SubmissionEntity submission = createSubmission("student-1");
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.singletonList(submission));
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        ArgumentCaptor<com.icongyou.enterprise.talent_analysis.entity.DataSyncLog> logCaptor = 
            ArgumentCaptor.forClass(com.icongyou.enterprise.talent_analysis.entity.DataSyncLog.class);
        verify(dataSyncLogMapper, atLeastOnce()).insert(logCaptor.capture());
        
        com.icongyou.enterprise.talent_analysis.entity.DataSyncLog log = logCaptor.getValue();
        assertNotNull(log.getId());
        assertEquals("submissions", log.getTableName());
        assertEquals("student-1", log.getStudentId());
        assertEquals("SYNC", log.getChangeType());
        assertNotNull(log.getSyncTime());
        assertNotNull(log.getRecordTime());
    }

    @Test
    @DisplayName("syncGrades - 无submissionId时跳过")
    void testSyncGrades_NullSubmissionId() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        
        GradeEntity grade = createGrade(null); // submissionId为null
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.singletonList(grade));

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        verify(dataAggregationMapper).selectAllNewGradesSince(any());
        // submissionId为null时不处理
        verify(dataAggregationMapper, never()).getAllStudentIds();
    }

    @Test
    @DisplayName("syncGrades - 查询学生失败时捕获异常")
    void testSyncGrades_QueryStudentException() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        
        GradeEntity grade = createGrade("submission-1");
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.singletonList(grade));
        when(dataAggregationMapper.getAllStudentIds())
            .thenThrow(new RuntimeException("数据库查询失败"));

        // Act
        scheduler.syncStudentDataChanges();

        // Assert - 异常被捕获，不影响整体流程
        verify(dataAggregationMapper).selectAllNewGradesSince(any());
    }

    @Test
    @DisplayName("syncSubmissions - 查询异常时继续处理")
    void testSyncSubmissions_QueryException() {
        // Arrange
        lenient().when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(LocalDateTime.now());
        lenient().when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(LocalDateTime.now());
        lenient().when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenThrow(new RuntimeException("查询失败"));
        lenient().when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act - 不应抛出异常
        assertDoesNotThrow(() -> scheduler.syncStudentDataChanges());

        // Assert - 异常被捕获,流程继续
    }

    @Test
    @DisplayName("syncSubmissions - 事件发布成功后记录日志")
    void testSyncSubmissions_EventPublishSuccess() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        SubmissionEntity submission = createSubmission("student-1");
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.singletonList(submission));
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        verify(eventPublisher, atLeastOnce()).publishEvent(any());
        verify(dataSyncLogMapper, atLeastOnce()).insert(any());
    }

    @Test
    @DisplayName("syncSubmissions - 大批量数据处理")
    void testSyncSubmissions_LargeBatch() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        List<SubmissionEntity> largeList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            largeList.add(createSubmission("student-" + (i % 20))); // 20个学生，每人5次提交
        }
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(largeList);
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        verify(eventPublisher, times(20)).publishEvent(any()); // 20个不同学生
        verify(dataSyncLogMapper, times(20)).insert(any());
    }

    @Test
    @DisplayName("syncGrades - 空成绩列表提前返回")
    void testSyncGrades_EmptyList() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        verify(dataAggregationMapper).selectAllNewGradesSince(any());
        verify(dataAggregationMapper, never()).getAllStudentIds();
    }

    @Test
    @DisplayName("syncSubmissions - 首次同步24小时窗口验证")
    void testSyncSubmissions_FirstSync24Hours() {
        // Arrange
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(null);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(LocalDateTime.now());
        
        ArgumentCaptor<LocalDateTime> timeCaptor = ArgumentCaptor.forClass(LocalDateTime.class);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(timeCaptor.capture()))
            .thenReturn(Collections.emptyList());
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        LocalDateTime capturedTime = timeCaptor.getValue();
        LocalDateTime now = LocalDateTime.now();
        assertTrue(capturedTime.isAfter(now.minusDays(1).minusMinutes(1)));
        assertTrue(capturedTime.isBefore(now.minusDays(1).plusMinutes(1)));
    }

    @Test
    @DisplayName("syncGrades - 首次同步24小时窗口验证")
    void testSyncGrades_FirstSync24Hours() {
        // Arrange
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(LocalDateTime.now());
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(null);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        
        ArgumentCaptor<LocalDateTime> timeCaptor = ArgumentCaptor.forClass(LocalDateTime.class);
        when(dataAggregationMapper.selectAllNewGradesSince(timeCaptor.capture()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        LocalDateTime capturedTime = timeCaptor.getValue();
        LocalDateTime now = LocalDateTime.now();
        assertTrue(capturedTime.isAfter(now.minusDays(1).minusMinutes(1)));
        assertTrue(capturedTime.isBefore(now.minusDays(1).plusMinutes(1)));
    }

    @Test
    @DisplayName("syncSubmissions - 混合个人和团队作业")
    void testSyncSubmissions_MixedIndividualAndTeam() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        List<SubmissionEntity> submissions = Arrays.asList(
            createSubmission("student-1"), // 个人作业
            createTeamSubmission("student-2", Arrays.asList("student-2", "student-3")), // 团队作业
            createSubmission("student-4") // 个人作业
        );
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(submissions);
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        ArgumentCaptor<StudentDataChangeEvent> eventCaptor = 
            ArgumentCaptor.forClass(StudentDataChangeEvent.class);
        verify(eventPublisher, times(4)).publishEvent(eventCaptor.capture()); // 4个学生
        
        List<String> studentIds = eventCaptor.getAllValues().stream()
            .map(StudentDataChangeEvent::getStudentId)
            .collect(java.util.stream.Collectors.toList());
        assertTrue(studentIds.contains("student-1"));
        assertTrue(studentIds.contains("student-2"));
        assertTrue(studentIds.contains("student-3"));
        assertTrue(studentIds.contains("student-4"));
    }

    @Test
    @DisplayName("syncSubmissions - 验证事件和日志按顺序执行")
    void testSyncSubmissions_EventAndLogSequence() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        SubmissionEntity submission = createSubmission("student-1");
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.singletonList(submission));
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert - 验证执行顺序
        org.mockito.InOrder inOrder = inOrder(eventPublisher, dataSyncLogMapper);
        inOrder.verify(eventPublisher).publishEvent(any(StudentDataChangeEvent.class));
        inOrder.verify(dataSyncLogMapper).insert(any());
    }

    @Test
    @DisplayName("syncGrades - 成功查找到学生并触发事件")
    void testSyncGrades_FindStudentSuccess() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        
        GradeEntity grade = createGrade("submission-123");
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.singletonList(grade));
        
        // Mock 学生查询链路
        when(dataAggregationMapper.getAllStudentIds())
            .thenReturn(Arrays.asList("student-1", "student-2"));
        
        var course = new com.icongyou.enterprise.data_aggregation.entity.CourseStudentEntity();
        course.setCourseId("course-1");
        when(dataAggregationMapper.getStudentCourse("student-1"))
            .thenReturn(Collections.singletonList(course));
        
        var assignment = new com.icongyou.enterprise.data_aggregation.entity.AssignmentStudentEntity();
        assignment.setAssignmentId("assignment-1");
        when(dataAggregationMapper.getStudentAssignmentScore("student-1", "course-1"))
            .thenReturn(Collections.singletonList(assignment));
        
        SubmissionEntity submission = createSubmission("student-1");
        submission.setSubmissionId("submission-123");
        when(dataAggregationMapper.getStudentSubmission("student-1", "assignment-1"))
            .thenReturn(Collections.singletonList(submission));
        
        when(dataAggregationMapper.getStudentCourse("student-2"))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        ArgumentCaptor<StudentDataChangeEvent> eventCaptor = 
            ArgumentCaptor.forClass(StudentDataChangeEvent.class);
        verify(eventPublisher, atLeastOnce()).publishEvent(eventCaptor.capture());
        
        boolean hasGradeEvent = eventCaptor.getAllValues().stream()
            .anyMatch(e -> "GRADE_SYNC".equals(e.getChangeType()));
        assertTrue(hasGradeEvent);
    }

    @Test
    @DisplayName("syncGrades - 团队成绩通知所有成员")
    void testSyncGrades_TeamGrade() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        
        GradeEntity grade = createGrade("submission-team-1");
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.singletonList(grade));
        
        when(dataAggregationMapper.getAllStudentIds())
            .thenReturn(Collections.singletonList("student-1"));
        
        var course = new com.icongyou.enterprise.data_aggregation.entity.CourseStudentEntity();
        course.setCourseId("course-1");
        when(dataAggregationMapper.getStudentCourse("student-1"))
            .thenReturn(Collections.singletonList(course));
        
        var assignment = new com.icongyou.enterprise.data_aggregation.entity.AssignmentStudentEntity();
        assignment.setAssignmentId("assignment-1");
        when(dataAggregationMapper.getStudentAssignmentScore("student-1", "course-1"))
            .thenReturn(Collections.singletonList(assignment));
        
        SubmissionEntity teamSubmission = createTeamSubmission("student-1", 
            Arrays.asList("student-1", "student-2"));
        teamSubmission.setSubmissionId("submission-team-1");
        when(dataAggregationMapper.getStudentSubmission("student-1", "assignment-1"))
            .thenReturn(Collections.singletonList(teamSubmission));

        // Act
        scheduler.syncStudentDataChanges();

        // Assert
        ArgumentCaptor<StudentDataChangeEvent> eventCaptor = 
            ArgumentCaptor.forClass(StudentDataChangeEvent.class);
        verify(eventPublisher, atLeast(2)).publishEvent(eventCaptor.capture());
    }

    @Test
    @DisplayName("syncGrades - 多条成绩记录批量处理")
    void testSyncGrades_MultipleGrades() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        
        List<GradeEntity> grades = Arrays.asList(
            createGrade("sub-1"),
            createGrade("sub-2"),
            createGrade(null) // 有一条没有submissionId
        );
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(grades);
        
        when(dataAggregationMapper.getAllStudentIds())
            .thenReturn(Arrays.asList("student-1"));

        // Act
        scheduler.syncStudentDataChanges();

        // Assert - 至少会查询学生ID
        verify(dataAggregationMapper, atLeast(1)).getAllStudentIds();
    }

    @Test
    @DisplayName("syncSubmissions - 空teamId但有contributionRate")
    void testSyncSubmissions_NullTeamIdWithContribution() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        
        SubmissionEntity submission = createSubmission("student-1");
        submission.setTeamId(null);
        List<SubmissionEntity.ContributionItem> contributions = new ArrayList<>();
        SubmissionEntity.ContributionItem item = new SubmissionEntity.ContributionItem();
        item.setStudentId("student-2");
        contributions.add(item);
        submission.setContributionRate(contributions);
        
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.singletonList(submission));
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.emptyList());

        // Act
        scheduler.syncStudentDataChanges();

        // Assert - 应该只通知主提交者
        ArgumentCaptor<StudentDataChangeEvent> eventCaptor = 
            ArgumentCaptor.forClass(StudentDataChangeEvent.class);
        verify(eventPublisher, times(1)).publishEvent(eventCaptor.capture());
        assertEquals("student-1", eventCaptor.getValue().getStudentId());
    }

    @Test
    @DisplayName("syncGrades - submission匹配失败继续查找")
    void testSyncGrades_SubmissionMismatch() {
        // Arrange
        LocalDateTime lastSync = LocalDateTime.now().minusHours(1);
        when(dataSyncLogMapper.getLastSyncTime("submissions")).thenReturn(lastSync);
        when(dataSyncLogMapper.getLastSyncTime("grades")).thenReturn(lastSync);
        when(dataAggregationMapper.selectAllNewSubmissionsSince(any()))
            .thenReturn(Collections.emptyList());
        
        GradeEntity grade = createGrade("target-submission");
        when(dataAggregationMapper.selectAllNewGradesSince(any()))
            .thenReturn(Collections.singletonList(grade));
        
        when(dataAggregationMapper.getAllStudentIds())
            .thenReturn(Collections.singletonList("student-1"));
        
        var course = new com.icongyou.enterprise.data_aggregation.entity.CourseStudentEntity();
        course.setCourseId("course-1");
        when(dataAggregationMapper.getStudentCourse("student-1"))
            .thenReturn(Collections.singletonList(course));
        
        var assignment = new com.icongyou.enterprise.data_aggregation.entity.AssignmentStudentEntity();
        assignment.setAssignmentId("assignment-1");
        when(dataAggregationMapper.getStudentAssignmentScore("student-1", "course-1"))
            .thenReturn(Collections.singletonList(assignment));
        
        // 返回的submission ID不匹配
        SubmissionEntity wrongSubmission = createSubmission("student-1");
        wrongSubmission.setSubmissionId("wrong-submission");
        when(dataAggregationMapper.getStudentSubmission("student-1", "assignment-1"))
            .thenReturn(Collections.singletonList(wrongSubmission));

        // Act
        scheduler.syncStudentDataChanges();

        // Assert - 查询被执行但没找到匹配的
        verify(dataAggregationMapper).getAllStudentIds();
        verify(dataAggregationMapper).getStudentCourse("student-1");
    }

    // ==================== Helper Methods ====================

    private SubmissionEntity createSubmission(String studentId) {
        SubmissionEntity submission = new SubmissionEntity();
        submission.setSubmissionId("sub-" + System.currentTimeMillis());
        submission.setStudentId(studentId);
        submission.setAssignmentId("assignment-1");
        submission.setSubmissionTime(LocalDateTime.now());
        return submission;
    }

    private SubmissionEntity createTeamSubmission(String mainStudentId, List<String> teamMemberIds) {
        SubmissionEntity submission = createSubmission(mainStudentId);
        submission.setTeamId("team-1");
        
        List<SubmissionEntity.ContributionItem> contributions = new ArrayList<>();
        for (String memberId : teamMemberIds) {
            SubmissionEntity.ContributionItem item = new SubmissionEntity.ContributionItem();
            item.setStudentId(memberId);
            item.setRate(java.math.BigDecimal.valueOf(1.0 / teamMemberIds.size()));
            contributions.add(item);
        }
        submission.setContributionRate(contributions);
        
        return submission;
    }

    private GradeEntity createGrade(String submissionId) {
        GradeEntity grade = new GradeEntity();
        grade.setGradeId("grade-" + System.currentTimeMillis());
        grade.setSubmissionId(submissionId);
        grade.setFinalScore(java.math.BigDecimal.valueOf(85.0));
        return grade;
    }
}
